<?php
/**
 * <https://y.st./>
 * Copyright © 2017 Alex Yst <mailto:copyright@y.st>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org./licenses/>.
**/

$xhtml = array(
	'<{title}>' => 'Unit7.java',
	'<{subtitle}>' => 'Written in <span title="Programming 2">CS 1103</span> of <a href="http://www.uopeople.edu/">University of the People</a>, finalised on 2017-08-02',
	'<{copyright year}>' => '2017',
	'takedown' => '2017-11-01',
	'<{body}>' => <<<END
<h2>Source code</h2>
<blockquote>
<pre><code>/* 
 * Copyright (C) 2017 Alex Yst
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.
 */
package st.y.CS1103.Unit7;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
 * Alex Yst&apos;s CS 1103 (Programming 2) Unit 6 submission
 * 
 * This program acts as a basic Web server, processing and responding to HTTP
 * requests.
 * 
 * @author Alex Yst
 */
public class Unit7 {
/**
 * Port 80 should be used, but this is only a demo. Using port 80 requires root
 * access, which isn&apos;t suitable for testing.
 */
	public static int LISTENING_PORT = 8080;
/**
 * Files will be served from ~/www , the &quot;www&quot; subdirectory of a user&apos;s home
 * directory. Again, this isn&apos;t good for a production server, but it&apos;s good for
 * a demo server such as this one. Before we get serious about serving files
 * anyway, we need to implement some basic TLS support.
 */
	public static String rootDirectory = System.getProperty(&quot;user.home&quot;) + &quot;/www&quot;;

	private static class ConnectionThread extends Thread {
		Socket connection;
		ConnectionThread(Socket connection) {
			this.connection = connection;
		}
		public void run() {
			handleConnection(connection);
		}
	}
/**
 * This method processes a client&apos;s request and provides the client with an
 * appropriate responce.
 * 
 * @param connection a Socket object returned by ServerSocket.accept()
 */
	public static void handleConnection(Socket connection) {
		try {
			Scanner input = new Scanner(connection.getInputStream());
			OutputStream outputStream = connection.getOutputStream();
			PrintWriter output = new PrintWriter(outputStream);
			String line = input.nextLine();
			String[] split = line.split(&quot; &quot;);
			File file = new File(rootDirectory + split[1]);
			if(file.isDirectory()) {
				split[1] += &quot;/index.xhtml&quot;;
				file = new File(rootDirectory + split[1]);
			}
			System.out.println(rootDirectory + split[1]);
// Our server is only programmed to handle GET requests. Anything else is not
// implemented, so a 501 NOT IMPLEMENTED status code should be returned.
			if(!split[0].equals(&quot;GET&quot;)) {
				handle501(output, split[0]);
			} else if(!split[2].equals(&quot;HTTP/1.1&quot;) &amp;&amp; !split[2].equals(&quot;HTTP/1.0&quot;)) {
				handle400(output, &quot;Protocol version, &amp;quot;&quot; + split[2] +&quot;&amp;quot;, not understood.&quot;);
			} else if(!split[1].substring(0, 1).equals(&quot;/&quot;)) {
				handle400(output, &quot;The request path must begin with a slash.&quot;);
			} else if(!file.exists()) {
				handle404(output);
			} else if(!file.canRead()) {
				handle403(output);
			} else {
				handle200(output, outputStream, split[1], file);
			}
			input.close();
			output.flush();
			output.close();
		} catch (Exception e) {
			System.out.println(&quot;Error communicating with client:&quot; + e.getMessage());
		} finally {
			try {
				connection.close();
			} catch(Exception e) {
				System.out.println(&quot;Error closing connection:&quot; + e.getMessage());
			}
		}
	}
/**
 * Normal, valid requests are filled by this method.
 * 
 * @param output The PrintWriter to write to
 * @param outputStream The OutputStream to pass to sendFile
 * @param filePath The file&apos;s path
 * @param file The File to send the client
 * @throws IOException This&apos;ll be handled by handleConnection()
 */
	public static void handle200(PrintWriter output, OutputStream outputStream, String filePath, File file) throws IOException {
		System.out.println(&quot;CODE: 200&quot;);
		sendHeaders(output, &quot;200 OK&quot;, (int) file.length(), getMimeType(filePath));
		sendFile(file, outputStream);
	}
/**
 * If a request is invalid, this method handles it.
 * 
 * @param output The PrintWriter to write to
 * @param errorMessage The error message
 */
	public static void handle400(PrintWriter output, String errorMessage) {
		String page = buildErrorPage(&quot;400 Bad Request&quot;, errorMessage);
		sendHeaders(output, &quot;400 Bad Request&quot;, page.length(), &quot;application/xhtml+xml&quot;);
		output.print(page);
		output.flush();
	}
/**
 * When files are not readable by the Web server, the client isn&apos;t allowed
 * access to them. This method responds to such cases.
 * 
 * @param output The PrintWriter to write to
 */
	public static void handle403(PrintWriter output) {
		String page = buildErrorPage(&quot;403 Forbidden&quot;, &quot;You do not have permission to view the requested file on this server.&quot;);
		sendHeaders(output, &quot;403 Forbidden&quot;, page.length(), &quot;application/xhtml+xml&quot;);
		output.print(page);
		output.flush();
	}
/**
 * When files are missing, this method provides a responce.
 * 
 * @param output The PrintWriter to write to
 */
	public static void handle404(PrintWriter output) {
		String page = buildErrorPage(&quot;404 Not Found&quot;, &quot;The requested file could not be found on this server.&quot;);
		sendHeaders(output, &quot;404 Not Found&quot;, page.length(), &quot;application/xhtml+xml&quot;);
		output.print(page);
		output.flush();
	}
/**
 * This simple server only implements the GET method in requests. Anything else
 * is handled by this method.
 * 
 * @param output The PrintWriter to write to
 * @param requestMethod The HTTP request method
 */
	public static void handle501(PrintWriter output, String requestMethod) {
		String page = buildErrorPage(&quot;501 Not Implemented&quot;, &quot;The &quot; + requestMethod + &quot; method is not supported by this server. This server only supports GET requests.&quot;);
		sendHeaders(output, &quot;501 Not Implemented&quot;, page.length(), &quot;application/xhtml+xml&quot;);
		output.print(page);
		output.flush();
	}
/**
 * This method sends the needed headers for responses.
 * 
 * @param output The PrintWriter to write to
 * @param status A status code and message
 * @param contentLength The length of the response body
 * @param contentType The content type of the response
 */
	public static void sendHeaders(PrintWriter output, String status, int contentLength, String contentType) {
		output.print(&quot;HTTP/1.1 &quot; + status + &quot;\\r\\n&quot;);
		output.print(&quot;Connection: close\\r\\n&quot;);
		output.print(&quot;Content-Type: &quot; + contentType + &quot;\\r\\n&quot;);
		output.print(&quot;Content-Length: &quot; + contentLength + &quot;\\r\\n&quot;);
		output.print(&quot;\\r\\n&quot;);
		output.flush();
	}
/**
 * This method constructs error pages as needed.
 * 
 * @param title The page &lt;title/&gt; and &lt;h1/&gt; message
 * @param body The message to include in the &lt;body/&gt;&apos;s &lt;p/&gt; element
 * @return The content of a page to send
 */
	public static String buildErrorPage(String title, String body) {
		return &quot;&lt;?xml version=\\&quot;1.0\\&quot; encoding=\\&quot;utf-8\\&quot;?&gt;\\n&quot; +
			&quot;&lt;!--\\n&quot; +
			&quot;	Copyright © 2017 Alex Yst &lt;mailto:copyright@y.st&gt;\\n&quot; +
			&quot;\\n&quot; +
			&quot;	This program is free software: you can redistribute it and/or modify\\n&quot; +
			&quot;	it under the terms of the GNU General Public License as published by\\n&quot; +
			&quot;	the Free Software Foundation, either version 3 of the License, or\\n&quot; +
			&quot;	(at your option) any later version.\\n&quot; +
			&quot;\\n&quot; +
			&quot;	This program is distributed in the hope that it will be useful,\\n&quot; +
			&quot;	but WITHOUT ANY WARRANTY; without even the implied warranty of\\n&quot; +
			&quot;	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\\n&quot; +
			&quot;	GNU General Public License for more details.\\n&quot; +
			&quot;\\n&quot; +
			&quot;	You should have received a copy of the GNU General Public License\\n&quot; +
			&quot;	along with this program. If not, see &lt;http://www.gnu.org./licenses/&gt;.\\n&quot; +
			&quot;--&gt;\\n&quot; +
			&quot;&lt;!DOCTYPE html&gt;\\n&quot; +
			&quot;&lt;html xmlns=\\&quot;http://www.w3.org/1999/xhtml\\&quot;&gt;\\n&quot; +
			&quot;	&lt;head&gt;\\n&quot; +
			&quot;		&lt;title&gt;&quot; + title + &quot;&lt;/title&gt;\\n&quot; +
			&quot;		&lt;meta name=\\&quot;viewport\\&quot; content=\\&quot;width=device-width\\&quot; /&gt;\\n&quot; +
			&quot;	&lt;/head&gt;\\n&quot; +
			&quot;	&lt;body&gt;\\n&quot; +
			&quot;		&lt;h1&gt;&quot; + title + &quot;&lt;/h1&gt;\\n&quot; +
			&quot;		&lt;p&gt;\\n&quot; +
			&quot;			&quot; + body + &quot;\\n&quot; +
			&quot;		&lt;/p&gt;\\n&quot; +
			&quot;	&lt;/body&gt;\\n&quot; +
			&quot;&lt;/html&gt;\\n&quot;;
	}
	public static void main(String[] args) {
		ServerSocket serverSocket;
		try {
			serverSocket = new ServerSocket(LISTENING_PORT);
		}
		catch (Exception e) {
			System.out.println(&quot;Failed to create listening socket.&quot;);
			return;
		}
		System.out.println(&quot;Listening on port &quot; + LISTENING_PORT);
		System.out.println(&quot;Files are being served from &quot; + rootDirectory);
		try {
			while (true) {
				Socket connection = serverSocket.accept();
				System.out.println(&quot;\\nConnection from &quot;
						+ connection.getRemoteSocketAddress());
				ConnectionThread thread = new ConnectionThread(connection);
				thread.start();
			}
		}
		catch (Exception e) {
			System.out.println(&quot;Server socket shut down unexpectedly!&quot;);
			System.out.println(&quot;Error: &quot; + e);
			System.out.println(&quot;Exiting.&quot;);
		}
		finally {
			try {
				serverSocket.close();
			} catch(IOException e){
				System.out.println(&quot;Error shutting down server socket.&quot;);
			}
		}
	}
	private static String getMimeType(String fileName) {
		int pos = fileName.lastIndexOf(&apos;.&apos;);
		if (pos &lt; 0)  // no file extension in name
			return &quot;x-application/x-unknown&quot;;
		String ext = fileName.substring(pos+1).toLowerCase();
		if (ext.equals(&quot;txt&quot;)) return &quot;text/plain&quot;;
		else if (ext.equals(&quot;html&quot;)) return &quot;text/html&quot;;
		else if (ext.equals(&quot;htm&quot;)) return &quot;text/html&quot;;
		else if (ext.equals(&quot;css&quot;)) return &quot;text/css&quot;;
		else if (ext.equals(&quot;js&quot;)) return &quot;text/javascript&quot;;
		else if (ext.equals(&quot;java&quot;)) return &quot;text/x-java&quot;;
		else if (ext.equals(&quot;jpeg&quot;)) return &quot;image/jpeg&quot;;
		else if (ext.equals(&quot;jpg&quot;)) return &quot;image/jpeg&quot;;
		else if (ext.equals(&quot;png&quot;)) return &quot;image/png&quot;;
		else if (ext.equals(&quot;gif&quot;)) return &quot;image/gif&quot;;
		else if (ext.equals(&quot;ico&quot;)) return &quot;image/x-icon&quot;;
		else if (ext.equals(&quot;class&quot;)) return &quot;application/java-vm&quot;;
		else if (ext.equals(&quot;jar&quot;)) return &quot;application/java-archive&quot;;
		else if (ext.equals(&quot;zip&quot;)) return &quot;application/zip&quot;;
		else if (ext.equals(&quot;xml&quot;)) return &quot;application/xml&quot;;
		else if (ext.equals(&quot;xhtml&quot;)) return &quot;application/xhtml+xml&quot;;
		else return &quot;x-application/x-unknown&quot;;
// Note:  x-application/x-unknown  is something made up;
// it will probably make the browser offer to save the file.
	}
	private static void sendFile(File file, OutputStream socketOut) throws IOException {
		InputStream in = new BufferedInputStream(new FileInputStream(file));
		OutputStream out = new BufferedOutputStream(socketOut);
		while (true) {
			int x = in.read(); // read one byte from file
			if (x &lt; 0)
				break; // end of file reached
			out.write(x);  // write the byte to the socket
		}
		out.flush();
		in.close();
		out.close();
	}
}</code></pre>
</blockquote>
END
);
